package mutate

import (
	"encoding/json"
	"fmt"
	"github.com/InVisionApp/conjungo"
	"github.com/Masterminds/sprig/v3"
	"github.com/gohobby/deepcopy"
	"github.com/wI2L/jsondiff"
	"gitverse.ru/synapse/kubelatte/pkg/api/common"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"gitverse.ru/synapse/kubelatte/pkg/match"
	"gitverse.ru/synapse/kubelatte/pkg/storage"
	"gitverse.ru/synapse/kubelatte/pkg/templates"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	injconf "gitverse.ru/synapse/kubelatte/pkg/webhook/config"
	"html/template"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	lbs "k8s.io/apimachinery/pkg/labels"
	"k8s.io/utils/strings/slices"
	"reflect"
	"regexp"
	"sigs.k8s.io/yaml"
	"strings"
)

const MergeStrategy = "merge"
const ReplaceStrategy = "replace"

var MC *MutateController

type MutateController struct {
}

func (r *MutateController) GetRenders(obj map[string]interface{}, arfields *common.ARFields) []util.RenderItem {
	var renders []util.RenderItem
	triggers := storage.Storage.GetTriggers()
	templs := storage.Storage.GetTemplates()

	annots := map[string]string{}
	labels := map[string]string{}
	kind := ""
	name := ""

	kd, ok := obj["kind"]
	if ok {
		kind = kd.(string)
	}

	metadata, ok := obj["metadata"]
	if ok {
		tmpAn, ok := metadata.(map[string]interface{})["annotations"]
		if ok {
			for k, v := range tmpAn.(map[string]interface{}) {
				annots[k] = v.(string)
			}
		}
		tmpLb, ok := metadata.(map[string]interface{})["labels"]
		if ok {
			for k, v := range tmpLb.(map[string]interface{}) {
				labels[k] = v.(string)
			}
		}
		tmpName, ok := metadata.(map[string]interface{})["name"]
		if ok {
			name = tmpName.(string)
		}
	}

	logs.Debugf("GetRenders check annotations: %v; labels: %v", annots, labels)

	for _, trigger := range triggers {
		for _, config := range trigger.Spec.SideEffectConfigs {
			if util.KbltSideEffect != "enabled" && util.KbltSideEffect != "true" {
				break
			}
			if !match.IsMustProcess(*arfields, config.Match) {
				logs.Debugf("Check side effect trigger %s match failed", config.Name)
				continue
			}
			for _, ref := range config.TemplateRefs {
				t, ok := templs[ref]
				if !ok {
					continue
				}

				engine := r.LoadTemplate(t)
				for _, engineTemplate := range engine.Templates() {
					var tempBuffer strings.Builder
					if len(engineTemplate.Templates()) == 0 {
						continue
					}
					err := engineTemplate.Execute(&tempBuffer, obj)
					if err != nil {
						logs.Error(fmt.Sprintf("MutateController: GetRenders failed %s", err))
						continue
					}
					renders = append(renders, util.RenderItem{
						Template: t,
						Render:   tempBuffer.String(),
						Action:   "MIGRATE",
					})
				}
			}
		}
		for _, config := range trigger.Spec.MutationConfigs {
			if !match.IsMustProcess(*arfields, config.Match) {
				logs.Debugf("Check mutation trigger %s match failed", config.Name)
				continue
			}

			if config.Match.IsEmpty() && !r.CheckMatchSelector(&config, annots, labels, kind, name) {
				logs.Debugf("Check trigger %s CheckMatchSelector failed", config.Name)
				continue
			}
			logs.Debugf("Check trigger %s match selector ok", config.Name)

			if len(config.TemplateRefs) == 0 && kind == "Pod" {
				renders = append(renders, r.getRendersForPod(templs, obj, config)...)
			}

			for _, ref := range config.TemplateRefs {
				if t, ok := templs[ref]; ok {
					renders = append(renders, r.applyTemplate(t, obj, config.UpdateStrategy)...)
				}
			}

			//if config.PluginRef != "" {
			//	renders = append(renders, util.RenderItem{
			//		PluginName: config.PluginRef,
			//	})
			//}
		}
	}
	return renders
}

func (r *MutateController) applyTemplate(template v1alpha1.Template, obj map[string]interface{}, strategy string) []util.RenderItem {
	var renders []util.RenderItem

	engine := r.LoadTemplate(template)
	logs.Debugf("loading tmpl %v, %v", template.Name, template.Spec.Data)
	for _, engineTemplate := range engine.Templates() {
		logs.Debugf("template %v", engineTemplate.Name())
		var tempBuffer strings.Builder
		err := engineTemplate.Execute(&tempBuffer, obj)
		if err != nil {
			logs.Error(fmt.Sprintf("MutateController: GetRenders failed %s", err))
			continue
		}
		renders = append(renders, util.RenderItem{Template: template, Render: tempBuffer.String(), Action: strategy})
	}
	return renders
}

// CheckMatchSelector Need replaced to universal Match allow function
func (r *MutateController) CheckMatchSelector(config *v1alpha1.MutationConfig, annotations, labels map[string]string, kind, name string) bool {
	logs.Debugf("CheckMatchSelector got annotations %s, labels %s ", annotations, labels)
	logs.Debugf("CheckMatchSelector ObjectSelector Kind %s, Name %s", kind, name)
	if config.ObjectSelector.Kind != "" && config.ObjectSelector.Name != "" {
		if kind == config.ObjectSelector.Kind {
			matches, err := regexp.Match(config.ObjectSelector.Name, []byte(name))
			if err != nil {
				logs.Error(fmt.Sprintf("MutateController: CheckMatchSelector failed %s", err))
			}
			if matches {
				logs.Debugf("CheckMatchSelector ObjectSelector Kind %s, Name %s checked ok", kind, name)
				return true
			}
		}
	}

	if labels != nil && len(config.LabelSelector.MatchExpressions) != 0 {
		labelSet := lbs.Set(labels)
		labelSelectors := metav1.LabelSelector{}

		for _, expression := range config.LabelSelector.MatchExpressions {

			labelSelectors.MatchExpressions = append(labelSelectors.MatchExpressions,
				metav1.LabelSelectorRequirement{
					Key:      GetSelectorKey(expression.Key, expression.Values),
					Operator: expression.Operator,
					Values:   expression.Values,
				},
			)

			sel, err := metav1.LabelSelectorAsSelector(&labelSelectors)
			if err != nil {
				logs.Error(fmt.Sprintf("MutateController: CheckMatchSelector failed %s", err))
			} else {
				return sel.Matches(labelSet)
			}
		}
	}

	if annotations != nil {
		triggerKey := GetSelectorKey(fmt.Sprintf("%s/%s", config.AnnotationNamespace, config.AnnotationTrigger), nil)
		triggerValue, ok := annotations[triggerKey]
		if ok {
			logs.Debugf("CheckMatchSelector Annotations annotation %s %s ok ", triggerKey, triggerValue)
			if triggerValue != "true" && triggerValue != "enabled" {
				logs.Error(
					fmt.Sprintf("MutateController: CheckMatchSelector failed skip creation cause annotation value is not valid"))
			} else {
				return true
			}
		}
	}

	return false
}

func (r *MutateController) LoadTemplate(t v1alpha1.Template) *template.Template {
	templateEngine := template.New("common").
		Delims("{{%", "%}}").
		Funcs(templates.GomplateFuncs).
		Funcs(sprig.TxtFuncMap())
	_, err := templateEngine.New(t.Name).Parse(t.Spec.Data)
	if err != nil {
		logs.Error(
			fmt.Sprintf("RenderController: ReloadTemplates failed %s", err))
		return nil
	}
	return templateEngine
}

func (r *MutateController) Mutate(obj map[string]interface{}, raw []byte, arfields *common.ARFields) []jsondiff.Operation {
	var operations []jsondiff.Operation

	renders := r.GetRenders(deepcopy.DeepCopy(obj).(map[string]interface{}), arfields)
	for _, templateRender := range renders {
		var newObj map[string]interface{}
		opts := conjungo.NewOptions()
		err := json.Unmarshal(raw, &newObj)
		if err != nil {
			logs.Errorf("Mutate: unmarshal error: %v", err)
			continue
		}

		var renderObj map[string]interface{}
		err = yaml.Unmarshal([]byte(templateRender.Render), &renderObj)
		if err != nil {
			logs.Errorf("yaml unmarshal error: %v", err)
			continue
		}

		if templateRender.Action != "replace" {
			opts.SetKindMergeFunc(
				reflect.Slice,

				func(t, s reflect.Value, o *conjungo.Options) (reflect.Value, error) {
					aT, _ := t.Interface().([]interface{})
					aS, _ := s.Interface().([]interface{})

					aT = util.MergeSliceCommon(aT, aS, true)

					return reflect.ValueOf(aT), nil
				},
			)
			opts.SetKindMergeFunc(
				reflect.Map,

				func(t, s reflect.Value, o *conjungo.Options) (reflect.Value, error) {
					aT, _ := t.Interface().(map[string]interface{})
					aS, _ := s.Interface().(map[string]interface{})

					aT = util.MergeStruct(aT, aS, true)

					return reflect.ValueOf(aT), nil
				},
			)
		} else {
			opts.SetKindMergeFunc(
				reflect.Slice,

				func(t, s reflect.Value, o *conjungo.Options) (reflect.Value, error) {
					aS, _ := s.Interface().([]interface{})
					return reflect.ValueOf(aS), nil
				},
			)
		}

		opts.Overwrite = templateRender.Action == "replace"
		renderObj = addMutatingLabel(renderObj)
		err = conjungo.Merge(&newObj, renderObj, opts)
		if err != nil {
			logs.Errorf("conjungo.Merge error: %v", err)
			continue
		}

		patch, err := jsondiff.Compare(obj, newObj)
		if err != nil {
			logs.Errorf("jsondiff.Compare error: %v", err)
			continue
		}

		for _, operation := range patch {
			operations = append(operations, operation)
		}
	}

	return operations
}

func addMutatingLabel(obj map[string]interface{}) map[string]interface{} {
	if obj == nil {
		return nil
	}
	if _, ok := obj["metadata"]; !ok {
		obj["metadata"] = make(map[string]interface{})
	}

	if _, ok := obj["metadata"].(map[string]interface{})["labels"]; !ok {
		obj["metadata"].(map[string]interface{})["labels"] = make(map[string]interface{})
	}

	obj["metadata"].(map[string]interface{})["labels"].(map[string]interface{})["kblt.mutation.resource"] = "true"
	return obj
}

func GetSelectorKey(key string, values []string) string {
	prefix := injconf.Prefix
	isMustPrefix := util.KbltPrefixEnabled
	isLabelException := len(util.KbltLabelExceptionValues) > 0 && key == "app"
	isIstioApp := false
	if isLabelException {
		for _, v := range util.KbltLabelExceptionValues {
			if slices.Contains(values, v) {
				isIstioApp = true
			}
		}
	}
	if isLabelException && isIstioApp {
		return key
	}
	if isMustPrefix == "true" && !strings.HasPrefix(key, prefix) {
		key = fmt.Sprintf("%s.%s", prefix, key)
	}
	return key
}
